package com.boot.mq.rabbit.xxlconf;

import lombok.SneakyThrows;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import static org.apache.zookeeper.server.auth.DigestAuthenticationProvider.generateDigest;


/**
 * ZooKeeper cfg client (Watcher + some utils)
 *
 * @author xuxueli 2015年8月26日21:36:43
 */
@Component
public class XxlConfManager implements InitializingBean, DisposableBean {

    private static Logger logger = LoggerFactory.getLogger(XxlConfManager.class);


    @Value("${xxl.conf.admin.zkaddress}")
    private String zkaddress;

    @Value("${xxl.conf.admin.zkpath}")
    private String zkpath;

    @Value("${xxl.conf.admin.zkUserName}")
    private String zkUserName;

    @Value("${xxl.conf.admin.zkPassword}")
    private String zkPassword;

    private String idPassword;


    private static final String digest = "digest";


    // ------------------------------ zookeeper client ------------------------------
    private static ZkClient zkClient = null;

    @Override
    public void afterPropertiesSet() throws Exception {
        logger.info(">>> 开始初始化ZkClient！");
        long start = System.currentTimeMillis();

        zkClient = new ZkClient(zkaddress, 15000);
        zkClient.setZkSerializer(new JsonZkSerializer());
        idPassword = zkUserName + ":" + zkPassword;
        addAuthInfo();

        long end = System.currentTimeMillis();
        logger.info(">>> 初始化ZkClient完成！用时：[{}] Millis", (end - start));
    }

    void addAuthInfo() {
        logger.info(">>> 添加digest权限信息。");
//        zkClient.addAuthInfo(digest, idPassword.getBytes());
    }

    @Override
    public void destroy() throws Exception {
        if (zkClient != null) {
            zkClient.close();
            zkClient = null;
        }
    }

    // ------------------------------ conf opt ------------------------------

    /**
     * set zk conf
     *
     * @param entId
     * @param key
     * @param data
     */
    @ResetAclIfNoauth
    public void set(String entId, String key, String data) {
        String path = keyToPath(entId, key);
        createOrSet(path, data);
    }

    @ResetAclIfNoauth
    public void set(String subPath, String data) {
        String path = zkpath + (subPath.startsWith("/") ? subPath : "/" + subPath);
        createOrSet(path,data);
    }

    @ResetAclIfNoauth
    public boolean setIfChanged(String subPath, String data, String expectValue) {
        if (!Objects.equals(expectValue, data)) {
            String path = zkpath + (subPath.startsWith("/") ? subPath : "/" + subPath);
            createOrSet(path, data);
            return true;
        }
        return false;
    }

    public String get(String subPath) {
        String path = zkpath + (subPath.isEmpty() || subPath.startsWith("/") ? subPath : "/" + subPath);
        return zkClient.readData(path);
    }

    public String getOrDefault(String subPath, String defaultValue) {
        try {
            String s = get(subPath);
            return s == null || s.isEmpty() ? defaultValue : s;
        } catch (ZkNoNodeException e) {
            logger.warn("获取subPath={}，报错={}。返回defaultValue={}", subPath, e, defaultValue);
            return defaultValue;
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * 如果节点的父节点不存在，则创建. (acl)
     * @param path
     * @param data
     */
    @SneakyThrows
    private void createOrSet(String path, String data) {
        String id = null;
        try {
            id = generateDigest(idPassword);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("zk生成idPassword失败。zkPassword=" + zkPassword + ",zkPassword=" + zkPassword);
        }
        // Acl权限
        List<ACL> acl = new ArrayList<>();
        acl.add(new ACL(ZooDefs.Perms.ALL, new Id("digest", id)));
        acl.add(new ACL(ZooDefs.Perms.READ, new Id("world", "anyone")));

        // 创建父节点
        createParentPathIfNotExist(path, acl);

        if (!zkClient.exists(path)) {
            // 子节点不存在，则create子节点(acl)
//            zkClient.createPersistent(path, data, acl);
            logger.debug(">>> zkClient create successed! {}={}", path, data);
        } else {
            // 存在，则writeData
            zkClient.writeData(path, data);
            logger.debug(">>> zkClient set successed! {}={}", path, data);
        }
    }

    /**
     * 如果节点的父节点不存在，则创建父节点。
     *
     * @param path
     * @param acl
     */
    private void createParentPathIfNotExist(String path, List<ACL> acl) {
        String parentNodePath = path.substring(0, path.lastIndexOf('/'));

        if (!zkClient.exists(parentNodePath)) {
            logger.debug(">>> zkClient 创建父节点 : parentNodePath={}", parentNodePath);
//            zkClient.createPersistent(parentNodePath, true, acl);
        }
    }


    /**
     * delete zk conf
     *
     * @param entId
     * @param key
     */
    @ResetAclIfNoauth
    public void delete(String entId, String key) {
        String path = keyToPath(entId, key);
        logger.debug(">>> zkClient delete : path={}", path);
        zkClient.deleteRecursive(path);
    }

    @ResetAclIfNoauth
    public void delete(String subPath) {
        String path = zkpath + (subPath.startsWith("/") ? subPath : "/" + subPath);
        logger.debug(">>> zkClient delete : path={}", path);
        zkClient.deleteRecursive(path);
    }

    public boolean exists(String entid, String key) {
        String path = keyToPath(entid, key);
        return zkClient.exists(path);
    }

    public boolean exists(String subPath) {
        String path = zkpath + (subPath.startsWith("/") ? subPath : "/" + subPath);
        return zkClient.exists(path);
    }

    /**
     * @param subPath
     * @return subPath节点的子节点
     * @see ZkClient#getChildren(String)
     * @throws KeeperException.NoNodeException 使用此方法之前，需要先判断节点是否存在，如果节点存在，再getChildren
     */
    public List<String> getChildren(String subPath) {
        String path;
        if (subPath == null || subPath.isEmpty()) {
            path = zkpath;
        } else {
            path = zkpath + (subPath.startsWith("/") ? subPath : "/" + subPath);
        }
        return _children(path, subPath);
    }

    /**
     * @param subPath
     * @param parentPath 自定义根节点
     * @return subPath节点的子节点
     * @see ZkClient#getChildren(String)
     * @throws KeeperException.NoNodeException 使用此方法之前，需要先判断节点是否存在，如果节点存在，再getChildren
     */
    public List<String> getChildren(String subPath, String parentPath) {
        String path;
        if (subPath == null || subPath.isEmpty()) {
            path = parentPath;
        } else {
            path = parentPath + (subPath.startsWith("/") ? subPath : "/" + subPath);
        }
        return _children(path, subPath);
    }

    private List<String> _children(String path, String subPath) {
        try {
            // 如果节点存在，但是节点没有子节点，则返回ArrayList但是size=0
            return zkClient.getChildren(path);
        } catch (Exception e) {
            // 如果节点不存在，则会报错。
            logger.warn(">>ZooKeeper【ls {}】 报错：{}", path, e.getMessage());
            if (!exists(subPath)) {
                throw e;
            }
            return Collections.emptyList();
        }
    }

    public boolean notExists(String entid, String key) {
        return !exists(entid, key);
    }


    // ------------------------------ key 2 path ------------------------------

    /**
     * 通过entId和表名转成zk的nodePath
     *
     * @param entId 企业Id
     * @param key   表名
     * @return
     */
    public String keyToPath(String entId, String key) {
        return zkpath + "/ent" + entId + "/" + key;
    }

}